package tools;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;

import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.w3c.dom.Node;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;

import common.Result;

import hitExceptions.*;
import coreModel.*;
import dataPersistence.TransactionManager;
import dataPersistence.dao.IProductGroupDAO;
import dataPersistence.dao.IStorageUnitDAO;
import dataPersistence.dto.ProductGroupDTO;

import java.io.File;
import java.io.FileNotFoundException;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @author Fantastic 4
 * 
 * A program to read an XML file and save a version of the HIT model as 
 * defined by the XML file.
 */

public class Importer {

	private static Map<Barcode, Product> productMap;
	private static boolean inDBMode;
	private static boolean error;
	private static void print(String message) {
		System.out.println(message);
	}

	private static void usage() {
		print("USAGE: java tools.Importer [-sql] <data-file>");
	}

	public static void reInitialize()
	{
	//	hit = HIT.getInstance();
	}
	
	/** Performs the import and saves to file/database.
	 * 
	 * @param args An array containing the arguments
	 */
	public static void main(String[] args) 
	{
//		hit = HIT.getInstance();
		
		if (args.length < 1 || args.length>2)
		{
			usage();
		}
		else
		{
		
			try 
			{
				String xmlFileString = new String(args[args.length-1]);
				File fXmlFile = new File(xmlFileString);
				DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
				DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
				Document doc = dBuilder.parse(fXmlFile);
				
				//set db mode
				if(args.length ==2 && args[0].equalsIgnoreCase("-sql"))
				{
					//flags for db
					inDBMode = true;
					error=false;
					System.out.println("in bd mode");
					HIT.getInstance().setDataPersistenceFactory(true);
					//clear the db for import
					TransactionManager.createDB();
					TransactionManager.begin();
				}
				else
				{
					inDBMode=false;
					System.out.println("in serial mode");
					HIT.getInstance().setDataPersistenceFactory(false);
				}
				
				Element root = doc.getDocumentElement();
				root.normalize();
				productMap = new HashMap<Barcode, Product>();

				print("Importing from " + xmlFileString);
				print("Importing general product list.");
				importProductInfo(root);
				print("Importing Storage Units");
				importStorageUnits(root);
				print("Importing Removed Items(aka Item History)");
				importRemovedItems(root);
				print("Saving to default file");
				HIT.getInstance().save();
				print("Import successful!");

			}
			catch (InvalidDataException e) 
			{
				print("Invalid Data Exception: " + e);
				error=true;
			}
			catch (InvalidOperationException e) 
			{
				print("Invalid Operation Exception: " + e);
				error=true;
			}
			catch (FileNotFoundException e)
			{
				if(!args[args.length-1].equalsIgnoreCase("-sql"))
					print("The specified file does not exist!");
				usage();
				error = true;
			}
			catch (Exception e) 
			{
				print("Exception thrown:");
				error=true;
				e.printStackTrace();
			}
			finally
			{
				if(inDBMode)
					TransactionManager.end(!error);
			}
		}
	}

	private static void importProductInfo(Element documentElement) throws InvalidDataException
	{
		NodeList children = documentElement.getChildNodes();
		for (int i=0; i<children.getLength(); i++)
		{
			if (children.item(i).getNodeName().compareTo("products") == 0)
			{
				NodeList productsChildren = children.item(i).getChildNodes();
				for (int j=0; j<productsChildren.getLength(); j++)
				{
					if (productsChildren.item(j).getNodeName().compareTo("product") == 0)
					{
						Node productNode = productsChildren.item(j);
						Product product = createProduct(productNode);
						try 
						{
							Barcode productBarcode = product.getBarcode(); 
							productMap.put(productBarcode, product);
							HIT.getInstance().getProductManager().addProductToAllProducts(product);
						}
						catch (IllegalArgumentException e)
						{
							print("Couldn't put product " + product.getBarcode() + " in root product container. Probably duplicate barcode.");
						}
					}
				}
			}
		}
	}

	private static void importRemovedItems(Element documentElement) throws InvalidDataException 
	{
		NodeList children = documentElement.getChildNodes();
		for (int i=0; i<children.getLength(); i++)
		{
			if (children.item(i).getNodeName().compareTo("item-history") == 0)
			{
				NodeList removedItemsChildren = children.item(i).getChildNodes();
				for (int j=0; j<removedItemsChildren.getLength(); j++)
				{
					if (removedItemsChildren.item(j).getNodeName().compareTo("item") == 0)
					{
						NamedNodeMap attrMap = removedItemsChildren.item(j).getAttributes();

						Node productBarcodeNode = attrMap.getNamedItem("product");
						Barcode productBarcode = new Barcode(productBarcodeNode.getNodeValue());

						Node entryDateNode = attrMap.getNamedItem("entry-date");
						DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT);
						Date entryDate = new Date();
						try 
						{
							entryDate = df.parse(entryDateNode.getNodeValue());
						} 
						catch (ParseException e) 
						{
							throw new InvalidDataException("Bad item entry-date import. Parse exception: " + e.getMessage());
						}

						Node exitDateNode = attrMap.getNamedItem("exit-time");
						df = new SimpleDateFormat("MM/dd/yyyy hh:mm a");
						Date exitDate = new Date();
						try 
						{
							exitDate = df.parse(exitDateNode.getNodeValue());
						} 
						catch (ParseException e) 
						{
							throw new InvalidDataException("Bad item exit-time import. Parse exception: " + e.getMessage());
						}

						Item newItem = new Item();
						newItem.setProduct(productMap.get(productBarcode));
						newItem.setEntryDate(entryDate);
						newItem.setExitTime(exitDate);



						HIT.getInstance().getItemHistory().addItem(newItem, true);
					}
				}
			}
		}
	}

	private static void importStorageUnits(Element documentElement) throws IllegalArgumentException, InvalidDataException, InvalidOperationException, Exception
	{
		NodeList suNodeList = documentElement.getElementsByTagName("storage-unit");
		for (int i=0; i<suNodeList.getLength(); i++)
		{
			Node suNode = suNodeList.item(i);
			NamedNodeMap attrMap = suNode.getAttributes();

			Node nameNode = attrMap.getNamedItem("name");
			String name = nameNode.getNodeValue();
			
			StorageUnit newStorageUnit = new StorageUnit(name);
			HIT.getInstance().addStorageUnit(newStorageUnit);
			newStorageUnit = HIT.getInstance().getStorageUnit(name);

			NodeList childNodeList = suNode.getChildNodes();
			for (int j=0; j<childNodeList.getLength(); j++)
			{
				Node child = childNodeList.item(j);
				if (child.getNodeName().compareTo("products") == 0)
				{
					importProductsIntoProductContainer(child, newStorageUnit);
				}
				else if (child.getNodeName().compareTo("items") == 0)
				{
					importItemsIntoProductContainer(child, newStorageUnit);
				}
				else if (child.getNodeName().compareTo("product-groups") == 0)
				{
					importProductGroupsIntoProductContainer(child, newStorageUnit);
				}
			}
		}
	}

	private static void importProductsIntoProductContainer(Node productsNode, ProductContainer productContainer) throws InvalidDataException
	{
		NodeList productNodeList = productsNode.getChildNodes();
		for (int i=0; i<productNodeList.getLength(); i++)
		{
			Node productNode = productNodeList.item(i);
			if (productNode.getNodeName().compareTo("product") == 0)
			{
				NamedNodeMap attrMap = productNode.getAttributes();

				Node barcodeNode = attrMap.getNamedItem("barcode");
				Barcode barcode = new Barcode(barcodeNode.getNodeValue());

				Product product = productMap.get(barcode);
				try
				{
					productContainer.addProduct(product);
				} 
				catch (InvalidOperationException e)
				{
					throw new InvalidDataException("Can't add that product to HIT");
				}
			}
		}
	}

	private static void importItemsIntoProductContainer(Node itemsNode, ProductContainer productContainer) throws InvalidDataException, InvalidOperationException
	{
		NodeList itemNodeList = itemsNode.getChildNodes();
		for (int i=0; i<itemNodeList.getLength(); i++)
		{
			Node itemNode = itemNodeList.item(i);
			if (itemNode.getNodeName().compareTo("item") == 0)
			{
				NamedNodeMap attrMap = itemNode.getAttributes();

				Node productBarcodeNode = attrMap.getNamedItem("product");
				Barcode productBarcode = new Barcode(productBarcodeNode.getNodeValue());

				Node dateNode = attrMap.getNamedItem("entry-date");
				DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT);
				Date date = new Date();
				try 
				{
					date = df.parse(dateNode.getNodeValue());
				} 
				catch (ParseException e) 
				{
					throw new InvalidDataException("Bad item entry-date import. Parse exception: " + e.getMessage());
				}

				Item newItem = new Item();
				newItem.setProduct(productMap.get(productBarcode));
				newItem.setEntryDate(date);
				newItem.setParentContainer(productContainer);

				//To verify code integrity. Does the product reference point correctly?
				if (productContainer.getProducts().contains(newItem.getProduct()) == false)
				{
					throw new InvalidDataException("Bad item product import. Product with barcode " 
							+ productBarcode.getValue() + " was not found in the storage unit " + productContainer.getName() + ".");
				}

				productContainer.addItem(newItem);
			}
		}
	}

	private static void importProductGroupsIntoProductContainer(Node productGroupsNode, ProductContainer productContainer) throws InvalidOperationException, Exception 
	{
		NodeList productGroupNodeList = productGroupsNode.getChildNodes();
		for (int i=0; i<productGroupNodeList.getLength(); i++)
		{
			Node productGroupNode = productGroupNodeList.item(i);
			if (productGroupNode.getNodeName().compareTo("product-group") == 0)
			{
				NamedNodeMap attrMap = productGroupNode.getAttributes();

				Node nameNode = attrMap.getNamedItem("name");
				String name = nameNode.getNodeValue();

				Node supplyNode = attrMap.getNamedItem("supply");
				String supplyString = supplyNode.getNodeValue();
				Supply threeMonths = parseSupply(supplyString,true);

				ProductGroup productGroup = new ProductGroup(name,productContainer, threeMonths);

				productContainer.addProductGroup(productGroup);
				if(inDBMode)
				{
					IProductGroupDAO dao = HIT.getInstance().getFactory().getProductGroupDAO();
					ProductGroupDTO dto = new ProductGroupDTO(productGroup);
					Result result = new Result();
					result=dao.add(dto);
					if(!result.getStatus())
					{
						System.err.println(result.getMessage());
					}
				}

				NodeList childNodeList = productGroupNode.getChildNodes();
				for (int j=0; j<childNodeList.getLength(); j++)
				{
					Node child = childNodeList.item(j);
					if (child.getNodeName().compareTo("products") == 0)
					{
						importProductsIntoProductContainer(child, productGroup);
					}
					else if (child.getNodeName().compareTo("items") == 0)
					{
						importItemsIntoProductContainer(child, productGroup);
					}
					else if (child.getNodeName().compareTo("product-groups") == 0)
					{
						importProductGroupsIntoProductContainer(child, productGroup);
					}
				}
			}
		}
	}

	private static Product createProduct(Node productNode) throws InvalidDataException
	{
		
		NamedNodeMap attrMap = productNode.getAttributes();

		Node barcodeNode = attrMap.getNamedItem("barcode");
		Barcode barcode = new Barcode(barcodeNode.getNodeValue());

		Node descriptionNode = attrMap.getNamedItem("description");
		String description = descriptionNode.getNodeValue();

		//moved product creation to this point in code to fix bug
		Product newProduct= new Product(description,barcode);
		
		
		Node shelfLifeNode = attrMap.getNamedItem("shelf-life");
		if(shelfLifeNode!=null)
		{
			int shelfLife = Integer.parseInt(shelfLifeNode.getNodeValue());
			newProduct.setShelfLife(shelfLife);
		}

		Node supplyNode = attrMap.getNamedItem("supply");
		if(supplyNode!=null)
		{
			int threeMonthSupply = Integer.parseInt(supplyNode.getNodeValue());
			newProduct.setThreeMonthSupply(threeMonthSupply);
		}
		
		Node sizeNode = attrMap.getNamedItem("size");
		String sizeString = sizeNode.getNodeValue();
		Supply productStats = parseSupply(sizeString,false);

		Node dateNode = attrMap.getNamedItem("creation-date");
		DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT);
		Date date = new Date();
		try 
		{
			date = df.parse(dateNode.getNodeValue());
		} 
		catch (ParseException e) 
		{
			throw new InvalidDataException("Bad product creation-date import. Parse exception: " + e.getMessage());
		}
		System.out.println(description);
		
		newProduct.setSupply(productStats);
		newProduct.setCreationDate(date);
		return newProduct;
	}

	private static Supply parseSupply(String supplyString,boolean isForPG) throws InvalidDataException
	{
		if(isForPG)
		{
			try
			{
				String[] sizeStrings = supplyString.split(" ");
				float size = Float.parseFloat(sizeStrings[0]);
				Unit sizeUnit = parseUnit(sizeStrings);
				return new Supply(size, sizeUnit);
			}
			catch(Exception e)
			{
				throw new InvalidDataException("Prodcut Group Supply must be an Integer");
			}
		}
		else
		{
			try
			{
			String[] sizeStrings = supplyString.split(" ");
			float size = Float.parseFloat(sizeStrings[0]);
			Unit sizeUnit = parseUnit(sizeStrings);
			return new Supply(size, sizeUnit);
			}
			catch(Exception e)
			{
				throw new InvalidDataException("Supply Value must be a Floating point number");
			}
		}
	}


	private static Unit parseUnit(String[] unitWords) throws InvalidDataException
	{
		Unit type;
		if (unitWords[1].compareTo("count") == 0)
			type = Unit.COUNT;
		else if (unitWords[1].compareTo("pounds") == 0)
			type = Unit.POUNDS;
		else if (unitWords[1].compareTo("ounces") == 0)
			type = Unit.OUNCES;
		else if (unitWords[1].compareTo("grams") == 0)
			type = Unit.GRAMS;
		else if (unitWords[1].compareTo("kilograms") == 0)
			type = Unit.KILOGRAMS;
		else if (unitWords[1].compareTo("gallons") == 0)
			type = Unit.GALLONS;
		else if (unitWords[1].compareTo("quarts") == 0)
			type = Unit.QUARTS;
		else if (unitWords[1].compareTo("pints") == 0)
			type = Unit.PINTS;
		else if (unitWords[1].compareTo("fluid") == 0 && unitWords[2].compareTo("ounces") == 0)
			type = Unit.FLUID_OUNCES;
		else if (unitWords[1].compareTo("liters") == 0)
			type = Unit.LITERS;
		else
		{
			if (unitWords.length == 3)
				throw new InvalidDataException("Bad unit import. Text: " + unitWords[0] + " " + unitWords[1] + unitWords[2] + ".");
			else
				throw new InvalidDataException("Bad unit import. Text: " + unitWords[0] + " " + unitWords[1] + ".");
		}
		return type; 
	}

}

